Skip to content

02 Vue 指令

指令 API — Vue.js

  • Vue 会根据不同的 指令,针对标签实现不同的 功能

  • 概念:指令(Directives)是 Vue 提供的带有 v- 前缀 的特殊标签属性

  • 为啥要学:提高程序员操作 DOM 的效率。学习不同指令 → 解决不同业务场景需求

  • vue 中的指令按照不同的用途可以分为如下 6 大类:

    • 内容渲染指令(v-htmlv-text
    • 条件渲染指令(v-showv-ifv-elsev-else-if
    • 事件绑定指令(v-on
    • 属性绑定指令(v-bind
    • 双向绑定指令(v-model
    • 列表渲染指令(v-for
  • 指令是 vue 开发中最基础、最常用、最简单的知识点。

内容渲染指令

  • 内容渲染指令用来辅助开发者渲染 DOM 元素的文本内容。

v-text

  • v-text(类似 innerText)

    • 使用语法:<p v-text="uname">hello</p>,意思是将 uame 值渲染到 p 标签中
    • 类似 innerText,使用该语法,会覆盖 p 标签原有内容

v-html

  • v-html(类似 innerHTML)

    • 使用语法:<p v-html="intro">hello</p>,意思是将 intro 值渲染到 p 标签中
    • 类似 innerHTML,使用该语法,会覆盖 p 标签原有内容
    • 类似 innerHTML,使用该语法,能够将 HTML 标签的样式呈现出来。

    Warning

    • 在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 XSS 攻击。只在可信内容上使用 v-html永不用在用户提交的内容上。
    • 单文件组件 里,scoped 的样式不会应用在 v-html 内部,因为那部分 HTML 没有被 Vue 的模板编译器处理。如果你希望针对 v-html 的内容设置带作用域的 CSS,你可以替换为 CSS Modules 或用一个额外的全局 <style> 元素手动设置类似 BEM 的作用域策略。
html
<div id="app">
  <h2>个人信息</h2>
  <!-- v-text 和 v-html 是 vue 提供的特殊 html 属性,所以可以直接当成属性来使用 -->
  <p v-text="uname">姓名:</p>
  <p v-html="intro">简介:</p>
</div>

<script src="./js/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      uname: "Black Mamba 黑曼巴",
      intro: "<span><a href='https://baike.baidu.com/item/科比·布莱恩特/318773'>牢大</a> 是个篮球运动员</span>"
    }
  });

  // v-text 和 v-html 的区别
  // 1. v-text 会将插值的内容,直接以文本的形式渲染到页面中
  // 2. v-html 会将插值的内容,以 html 的形式渲染到页面中
  console.log(app.uname);
  console.log(app.intro);
</script>

条件渲染指令

条件判断指令,用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有如下两个,分别是:v-showv-if。(v-elsev-else-if 是辅助 v-if 进行判断渲染)

v-show

  • v-show

    • 作用:控制元素显示隐藏
    • 语法:v-show="表达式" 表达式值为 true 显示false 隐藏
    • 原理:切换 display:nonedisplay:block 控制显示隐藏
    • 场景:频繁切换显示隐藏的场景

v-if

  • v-if

    • 作用:控制元素显示隐藏(条件渲染
    • 语法:v-if="表达式" 表达式值 true 显示false 隐藏
    • 原理:基于条件判断,是否创建移除元素节点
    • 场景:要么显示,要么隐藏,不频繁切换的场景
html
<!--
  v-show 底层原理:切换 css 的 display: none 来控制显示隐藏
  v-if  底层原理:根据 判断条件 控制元素的 创建 和 移除(条件渲染)
-->
<div id="app">
  <div v-show="flag" class="box">我是 v-show 控制的盒子</div>
  <div v-if="flag" class="box">我是 v-if 控制的盒子</div>
  <button @click="flag = !flag">切换显示隐藏</button>
</div>

<script src="./js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      flag: true
    }
  });

  console.log(app.flag);
  // 查看页面源代码,发现
  // v-show 控制的盒子,始终存在于页面中,只是 display: none
  // v-if 控制的盒子,当 flag 为 true 时,会显示,当 flag 为 false 时,会隐藏
</script>

v-else 和 v-else-if

  • v-elsev-else-if

    • 作用:辅助 v-if 进行判断渲染
    • 语法:v-else v-else-if="表达式"
    • 需要紧接着 v-if 使用
html
<div id="app">
  <div class="box box1">
    <ul>
      <li>name: <code>{{name}}</code></li>
      <li>age: {{age}}</li>
      <li>gender: {{gender}}</li>
      <li>score: {{score}}</li>
    </ul>
    <hr />
    <p v-text="name"></p>
    <p v-if="gender === 'male'">性别:♂ 男</p>
    <p v-else>性别:♀ 女</p>
    <p v-show="age >= 18">成年:✅</p>
    <p v-if="scoore >= 90">成绩评定 A:奖励电脑一台</p>
    <p v-else-if="scoore >= 70">成绩评定 B:奖励周末郊游</p>
    <p v-else-if="scoore >= 60">成绩评定 C:奖励零食礼包</p>
    <p v-else>成绩评定 D:惩罚一周不能玩手机</p>
  </div>
  <div class="box box2">
    <ul>
      <li>name: <code>{{name}}</code></li>
      <li>age: {{age}}</li>
      <li>gender: {{gender}}</li>
      <li>score: {{score}}</li>
    </ul>
    <hr />
    <p v-html="name"></p>
    <p v-if="gender === 'male'">性别:♂ 男</p>
    <p v-else>性别:♀ 女</p>
    <p v-if="scoore >= 90">成绩评定 A:奖励电脑一台</p>
    <p v-else-if="scoore >= 70">成绩评定 B:奖励周末郊游</p>
    <p v-else-if="scoore >= 60">成绩评定 C:奖励零食礼包</p>
    <p v-else>成绩评定 D:惩罚一周不能玩手机</p>
  </div>
</div>

<script src="./js/vue.js"></script>
<script>
  const box1 = new Vue({
    el: ".box1",
    data: {
      name: "Black Mamba 黑曼巴",
      age: 20,
      gender: "male",
      scoore: 80,
    },
  });

  const box2 = new Vue({
    el: ".box2",
    data: {
      name: "<a href='#'>李可儿</a>",
      age: 13,
      gender: "female",
      scoore: 98,
    },
  });
</script>

事件绑定指令

  • 作用:注册事件 = 添加监听提供处理逻辑

v-on

  • v-on

    • 使用 Vue 时,如需为 DOM 注册事件。

    • <button v-on:事件名="内联语句">按钮</button>

    • <button v-on:事件名="处理函数">按钮</button>

      • 事件处理函数应该写到一个跟 data 同级的配置项 methods
      • methods 处理函数内的 this 指向 Vue 实例
    • <button v-on:事件名="处理函数(实参)">按钮</button>

      • 如果不传递任何参数,则方法无需加小括号;methods 方法中可以直接使用 e 当做事件对象
      • 如果传递了参数,则实参 $event 表示事件对象,固定用法。
    • v-on: 简写为 @

html
<div id="app">
  <button @click="count++">+</button>
  <span>{{count}}</span>
  <button @click="count--">-</button>
</div>

<script src="./js/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      count: 99,
    },
  });

  console.log(app.count);
</script>
html
<div id="app">
  <button @click="showMsg">切换显示隐藏</button>
  <p v-show="flag" class="box">hello vue~</p>
</div>

<script src="./js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      flag: true
    },
    methods: {
      // showMsg 函数中,this 指向的是 app 实例
      showMsg() {
        this.flag = !this.flag;
      }
    }
  });
</script>
html
<div id="app">
  <div class="box">
    <h3>小黑自动售货机</h3>
    <button @click="buy(5)" v-if="money >= 5">可乐 5 元</button>
    <button @click="buy(10)" v-if="money >= 10">咖啡 10 元</button>
    <button @click="buy(8)" v-if="money >= 8">牛奶 8 元</button>
  </div>
  <p>银行卡余额:{{ money }}元</p>
</div>
<script src="./js/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      money: 20,
    },
    methods: {
      buy(price) {
        this.money -= price;
      },
    },
  });
</script>

属性绑定指令

v-bind

  • v-bind

  • 作用: 动态设置 html 的标签属性 比如:srcurltitle

    • 语法v-bind:属性名='表达式'
    • v-bind: 可以简写成 :
    • <img v-bind:src="url" /> => <img :src="url" />v-bind 可以省略)
html
<div id="app">
  <img :src="imgUrl" alt="" :title="imgTitle">
</div>
<script src="./js/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      // 随机图片
      imgUrl: "https://via.placeholder.com/200x200",
      imgTitle: "Black Mamba 黑曼巴",
    },
  });
</script>

案例 波仔的学习之旅

需求:默认展示数组中的第一张图片,点击上一页下一页来回切换数组中的图片

实现思路:

  1. 数组存储图片路径 ['url1','url2','url3',…]
  2. 可以准备个下标 index 去数组中取图片地址。
  3. 通过 v-bindsrc 绑定当前的图片地址
  4. 点击上一页下一页只需要修改下标的值即可
  5. 当展示第一张的时候,上一页按钮应该隐藏。展示最后一张的时候,下一页按钮应该隐藏
html
<div id="app">
  <button @click="index--" v-if="index > 0">prev</button>
  <img :src="imgUrl[index]" alt="" />
  <button @click="index++" v-if="index < imgUrl.length - 1">next</button>
</div>
<script src="./js/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      index: 0,
      imgUrl: [
        "https://s2.loli.net/2024/01/17/aL2Wr9tsTeukFJB.png",
        "https://s2.loli.net/2024/01/17/AdNXS9TtMRxHUaE.png",
        "https://s2.loli.net/2024/01/17/hqEmJvTRuctAWI7.gif",
        "https://s2.loli.net/2024/01/17/7Bmg8f9jKMbWhuQ.gif",
        "https://s2.loli.net/2024/01/17/iw41FNkYr6ohGOC.gif",
        "https://s2.loli.net/2024/01/17/R9scXpMECuaVHbg.gif",
      ],
    },
  });
</script>

列表渲染指令

Vue 提供了 v-for 列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。

v-for

v-for 指令需要使用 (item, index) in arr 形式的特殊语法,其中:

  • item 是数组中的每一项
  • index 是每一项的索引,不需要可以省略
  • arr 是被遍历的数组

此语法也可以遍历对象和数字

html
<div id="app">
  <h3>小黑水果店</h3>
  <ul>
    <!-- <li>西瓜 - 0</li> -->
    <!-- <li>苹果 - 1</li> -->
    <li v-for="(item, index) in fruits" :key="index">{{item}} - {{index}}</li>
  </ul>
</div>
<script src="./js/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      fruits: ["西瓜", "苹果", "香蕉", "橘子", "梨子", "葡萄"],
    },
  });
</script>

案例 小黑的书架

需求:

  1. 根据左侧数据渲染出右侧列表 v-for
  2. 点击删除按钮时,应该把当前行从列表中删除(获取当前行的 id,利用 filter 进行过滤)
html
<div id="app">
  <h3>小黑的书架</h3>
  <ul>
    <li v-for="(item, index) in books" :key="item.id">
      <span>{{index + 1}}.</span>
      <span class="book-name">{{item.name}}</span>
      <span class="book-author">{{item.author}}</span>
      <button @click="removeBook(item.id)">删除</button>
    </li>
  </ul>
</div>

<script src="./js/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      books: [
        { id: 1, name: '《红楼梦》', author: '曹雪芹' },
        { id: 2, name: '《西游记》', author: '吴承恩' },
        { id: 3, name: '《水浒传》', author: '施耐庵' },
        { id: 4, name: '《三国演义》', author: '罗贯中' }
      ]
    },
    methods: {
      removeBook(id) {
        // filter() 方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素
        // 接收一个函数作为参数,该函数用于测试数组中的每个元素
        // 如果函数返回 true,该元素将被包含在新数组中;否则,该元素将被排除
        this.books = this.books.filter(item => item.id !== id)
      }
    }
  });
</script>

v-for 中的 key

  • 语法: key="唯一值"
  • 作用:给列表项添加的唯一标识。便于 Vue 进行列表项的正确排序复用
  • **为什么加 key:**Vue 的默认行为会尝试原地修改元素(就地复用

注意

  1. key 的值只能是字符串 或 数字类型
  2. key 的值必须具有唯一性
  3. 推荐使用 id 作为 key(唯一),不推荐使用 index 作为 key(index 会变化,不对应)
html
<div id="app">
  <h3>小黑的书架</h3>
  <div class="box box1">
    <ul>
      <span class="keyword">有 key</span>
      <li v-for="(item, index) in books" :key="item.id">
        <span>{{index + 1}}.</span>
        <span class="book-name">{{item.name}}</span>
        <span class="book-author">{{item.author}}</span>
        <button @click="removeBook(item.id)">删除</button>
      </li>
    </ul>
  </div>
  <div class="box box2">
    <ul>
      <span class="keyword">无 key</span>
      <li v-for="(item, index) in books">
        <span>{{index + 1}}.</span>
        <span class="book-name">{{item.name}}</span>
        <span class="book-author">{{item.author}}</span>
        <button @click="removeBook(item.id)">删除</button>
      </li>
    </ul>
  </div>
</div>

<script src="./js/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      books: [
        { id: 1, name: '《红楼梦》', author: '曹雪芹' },
        { id: 2, name: '《西游记》', author: '吴承恩' },
        { id: 3, name: '《水浒传》', author: '施耐庵' },
        { id: 4, name: '《三国演义》', author: '罗贯中' }
      ]
    },
    methods: {
      removeBook(id) {
        // filter() 方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素
        // 接收一个函数作为参数,该函数用于测试数组中的每个元素
        // 如果函数返回 true,该元素将被包含在新数组中;否则,该元素将被排除
        this.books = this.books.filter(item => item.id !== id)
      }
    }
  });

  // 去控制台给第一个 li 标签添加 `background-color: pink;` 行内样式
  // 然后点击删除按钮,会发现>无 key 的第一个 li 标签的背景色没有被删除
  // 这是因为没有给 v-for 指令添加 key 属性,导致 Vue 无法识别每个 li 标签的唯一性
  document.querySelector('.box1 li:nth-of-type(1)').style.backgroundColor = 'pink'
  document.querySelector('.box2 li:nth-of-type(1)').style.backgroundColor = 'pink'
</script>

双向绑定指令

所谓双向绑定就是:

  1. 数据改变后,呈现的页面结果会更新
  2. 页面结果更新后,数据也会随之而变

v-model

  • v-model

    • 作用:表单元素inputradioselect)使用,双向绑定数据,可以快速 获取设置 表单元素内容

    • 语法: v-model="变量"

    • 需求: 使用双向绑定实现以下需求

      1. 点击登录按钮获取表单中的内容
      2. 点击重置按钮清空表单中的内容
html
<!--
    v-model 可以让数据和视图,形成双向数据绑定
      1. 数据变化,视图自动更新
      2. 视图变化,数据自动更新
    可以快速 [获取] 或 [设置] 表单元素的内容
-->
<div id="app">
  账户:<input type="text" v-model="uname" placeholder="请输入账户名称" />
  <br /><br />
  密码:<input type="password" v-model="passwd" placeholder="请输入账户密码" />
  <br /><br />
  <button @click="login">登录</button>
  <button @click="reset">重置</button>
</div>
<script src="./js/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      uname: "",
      passwd: "",
    },
    methods: {
      login() {
        console.log(this.uname, this.passwd);
      },
      reset() {
        this.uname = "";
        this.passwd = "";
      },
    },
  });
</script>

综合案例 小黑记事本

列表渲染

  • Vue 实例中的 data 中准备数据用于列表初始渲染
  • .todo-list 列表中的 li 元素使用 v-for 指令,遍历数组,渲染列表项
  • {{item.name}} 使用插值表达式,渲染数据
jsx
data: {
   // 准备数据用于列表初始渲染
   todos: [
     { id: 1, name: '学习 HTML' },
     { id: 2, name: '学习 CSS' },
     { id: 3, name: '学习 JavaScript' },
     { id: 4, name: '学习 Vue' },
   ]
}

<ul class="todo-list">
  <!-- 列表项,使用 v-for 指令,遍历数组,渲染列表项 -->
  <li class="todo" v-for="(item, index) in todos" :key="item.id">
    <div class="view">
      <span class="index">{{item.index + 1}}.</span>
      <label>{{item.name}}</label>
      <button @click="remove(item.id)" class="destroy"></button>
    </div>
  </li>
</ul>

删除功能

  • button 按钮使用 @click="remove(item.id)" (v-on:click="remove(item.id)" 的简写) 绑定点击事件
  • Vue 实例中添加 methods 事件处理函数。
  • todos.filter 方法用于过滤数组,返回一个新数组,不会修改原数组
  • todos.findIndex 方法用于查找数组中满足条件的元素,返回满足条件的元素的索引,如果没有找到,返回 -1
  • todos.splice 方法用于删除数组中的元素,第一个参数是索引,第二个参数是删除的个数
jsx
<button @click="remove(item.id)" class="destroy"></button>

methods: {
  // 删除任务
  remove(id) {
    // this.todos = this.todos.filter(item => item.id !== id)
    // 根据 id 找到对应的索引
    const index = this.todos.findIndex(item => item.id === id)
    // 根据索引,删除对应的元素
    // splice() 方法用于删除数组中的元素,第一个参数是索引,第二个参数是删除的个数
    this.todos.splice(index, 1)
 }
}

添加功能

  • input 输入框使用 v-model 指令,双向绑定数据
  • data 中准备初始数据用于绑定
  • button 按钮使用 @click="add" (v-on:click="add" 的简写) 绑定点击事件
  • Vue 实例中添加 methods 事件处理函数。
  • todos.push 方法用于向数组中添加元素
js
<input v-model="newTodo" placeholder="请输入任务" class="new-todo" />

data: {
  // 准备数据用于绑定
  newTodo: ''
}

<button @click="addTodo" class="add">添加任务</button>

methods: {
  // 添加任务
  addTodo() {
    // 判断输入框中是否有内容
    if (this.newTodo.trim().length === 0) console.log('输入任务名称不能为空') return
    // 准备数据
    const todo = {
        id: Date.now(),
        name: this.newTodo.trim()
    }
    // 添加到数组中
    this.todos.push(todo)
    // 清空输入框
    this.newTodo = ''
  }
}

底部统计 和 清空

  • 底部统计使用 v-html 指令 (或者直接使用 插值表达式),渲染数据
  • 清空按钮使用 @click="clear" (v-on:click="clear" 的简写) 绑定点击事件
  • todos 中没有任务时 (todos.length 长度为 0),使用 v-show 指令隐藏底部统计和清空按钮
jsx
<span class="todo-count">合 计:<strong> {{todos.length}} </strong></span>
<button @click="clearTodos" class="clear-completed">清空任务</button>

// 清空任务
clearTodos() {
  // 清空数组
  this.todos = []
}

<footer class="footer" v-show="todos.length > 0">

功能总结

  • 列表渲染

    • v-for key 的设置
    • 插值表达式
  • 删除功能

    • v-on 调用传参
    • filter 过滤 覆盖修改原数组
  • 添加功能

    • v-model 绑定
    • push 修改原数组添加
  • 底部统计和清空

    • 数组.length 累计长度
    • v-show 控制隐藏
    • 覆盖数组清空列表
html
<!-- 主体区域 -->
<section id="app">
  <!-- 输入框 -->
  <header class="header">
    <h1>小黑记事本</h1>
    <input placeholder="请输入任务" class="new-todo" />
    <button class="add">添加任务</button>
  </header>
  <!-- 列表区域 -->
  <section class="main">
    <ul class="todo-list">
      <li class="todo">
        <div class="view">
          <span class="index">1.</span> <label>吃饭饭</label>
          <button class="destroy"></button>
        </div>
      </li>
    </ul>
  </section>
  <!-- 统计和清空 -->
  <footer class="footer">
    <!-- 统计 -->
    <span class="todo-count">合 计:<strong> 1 </strong></span>
    <!-- 清空 -->
    <button class="clear-completed">清空任务</button>
  </footer>
</section>

<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {},
  });
</script>
html
<!-- 主体区域 -->
<section id="app">
  <!-- 输入框 -->
  <header class="header">
    <h1>小黑记事本</h1>
    <input placeholder="请输入任务" class="new-todo" />
    <button class="add">添加任务</button>
  </header>
  <!-- 列表区域 -->
  <section class="main">
    <ul class="todo-list">
      <!-- 列表项,使用 v-for 指令,遍历数组,渲染列表项 -->
      <li class="todo" v-for="(item, index) in todos" :key="item.id">
        <div class="view">
          <span class="index">{{index+1}}.</span>
          <label>{{item.name}}</label>
          <button class="destroy"></button>
        </div>
      </li>
    </ul>
  </section>
  <!-- 统计和清空 -->
  <footer class="footer">
    <!-- 统计 -->
    <span class="todo-count">合 计:<strong> 1 </strong></span>
    <!-- 清空 -->
    <button class="clear-completed">清空任务</button>
  </footer>
</section>

<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      // 准备数据用于列表初始渲染
      todos: [
        { id: 1, name: '学习 HTML' },
        { id: 2, name: '学习 CSS' },
        { id: 3, name: '学习 JavaScript' },
        { id: 4, name: '学习 Vue' },
      ]
    },
  });
</script>
html
<!-- 主体区域 -->
<section id="app">
  <!-- 输入框 -->
  <header class="header">
    <h1>小黑记事本</h1>
    <input v-model="newTodo" placeholder="请输入任务" class="new-todo" />
    <button @click="addTodo" class="add">添加任务</button>
  </header>
  <!-- 列表区域 -->
  <section class="main">
    <ul class="todo-list">
      <!-- 列表项,使用 v-for 指令,遍历数组,渲染列表项 -->
      <li class="todo" v-for="(item, index) in todos" :key="item.id">
        <div class="view">
          <span class="index">{{index+1}}.</span>
          <label>{{item.name}}</label>
          <button class="destroy" @click="removeTodo(item.id)"></button>
        </div>
      </li>
    </ul>
  </section>
  <!-- 统计和清空 -->
  <footer class="footer">
    <!-- 统计 -->
    <span class="todo-count">合 计:<strong> 1 </strong></span>
    <!-- 清空 -->
    <button class="clear-completed">清空任务</button>
  </footer>
</section>

<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      // 准备数据用于列表初始渲染
      newTodo: '',
      todos: [
        { id: 1, name: '学习 HTML' },
        { id: 2, name: '学习 CSS' },
        { id: 3, name: '学习 JavaScript' },
        { id: 4, name: '学习 Vue' },
      ]
    },
    methods: {
      removeTodo(id) {
        // this.todos = this.todos.filter(item => item.id !== id)
        // 根据 id 找到对应的索引
        const index = this.todos.findIndex(item => item.id === id)
        // 根据索引,删除对应的元素
        // splice() 方法用于删除数组中的元素,第一个参数是索引,第二个参数是删除的个数
        this.todos.splice(index, 1)
      },

      addTodo() {
        if (this.newTodo.trim().length === 0) {
          console.log('输入任务名称不能为空');
          return;
        }

        const todo = {
          id: +Date.now(),
          name: this.newTodo.trim()
        }
        this.todos.push(todo)
        this.newTodo = ''
      }
    }
  });
</script>
html
<!-- 主体区域 -->
<section id="app">
  <!-- 输入框 -->
  <header class="header">
    <h1>小黑记事本</h1>
    <input placeholder="请输入任务" class="new-todo" />
    <button class="add">添加任务</button>
  </header>
  <!-- 列表区域 -->
  <section class="main">
    <ul class="todo-list">
      <!-- 列表项,使用 v-for 指令,遍历数组,渲染列表项 -->
      <li class="todo" v-for="(item, index) in todos" :key="item.id">
        <div class="view">
          <span class="index">{{index+1}}.</span>
          <label>{{item.name}}</label>
          <button class="destroy" @click="removeTodo(item.id)"></button>
        </div>
      </li>
    </ul>
  </section>
  <!-- 统计和清空 -->
  <footer class="footer">
    <!-- 统计 -->
    <span class="todo-count">合 计:<strong> 1 </strong></span>
    <!-- 清空 -->
    <button class="clear-completed">清空任务</button>
  </footer>
</section>

<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      // 准备数据用于列表初始渲染
      todos: [
        { id: 1, name: '学习 HTML' },
        { id: 2, name: '学习 CSS' },
        { id: 3, name: '学习 JavaScript' },
        { id: 4, name: '学习 Vue' },
      ]
    },
    methods: {
      removeTodo(id) {
        // this.todos = this.todos.filter(item => item.id !== id)
        // 根据 id 找到对应的索引
        const index = this.todos.findIndex(item => item.id === id)
        // 根据索引,删除对应的元素
        // splice() 方法用于删除数组中的元素,第一个参数是索引,第二个参数是删除的个数
        this.todos.splice(index, 1)
      }
    }
  });
</script>
html
<!-- 主体区域 -->
<section id="app">
  <!-- 输入框 -->
  <header class="header">
    <h1>小黑记事本</h1>
    <input v-model="newTodo" placeholder="请输入任务" class="new-todo" />
    <button @click="addTodo" class="add">添加任务</button>
  </header>
  <!-- 列表区域 -->
  <section class="main">
    <ul class="todo-list">
      <!-- 列表项,使用 v-for 指令,遍历数组,渲染列表项 -->
      <li class="todo" v-for="(item, index) in todos" :key="item.id">
        <div class="view">
          <span class="index">{{index+1}}.</span>
          <label>{{item.name}}</label>
          <button class="destroy" @click="removeTodo(item.id)"></button>
        </div>
      </li>
    </ul>
  </section>
  <!-- 统计和清空 -->
  <footer class="footer" v-show="todos.length > 0">
    <!-- 统计 -->
    <span class="todo-count">合 计:<strong> {{todos.length}} </strong></span>
    <!-- 清空 -->
    <button @click="clearTodos" class="clear-completed">清空任务</button>
  </footer>
</section>

<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      // 准备数据用于列表初始渲染
      newTodo: "",
      todos: [
        { id: 1, name: "学习 HTML" },
        { id: 2, name: "学习 CSS" },
        { id: 3, name: "学习 JavaScript" },
        { id: 4, name: "学习 Vue" },
      ],
    },
    methods: {
      removeTodo(id) {
        // this.todos = this.todos.filter(item => item.id !== id)
        // 根据 id 找到对应的索引
        const index = this.todos.findIndex((item) => item.id === id);
        // 根据索引,删除对应的元素
        // splice() 方法用于删除数组中的元素,第一个参数是索引,第二个参数是删除的个数
        this.todos.splice(index, 1);
      },

      addTodo() {
        // 判断输入框中是否有内容
        if (this.newTodo.trim().length === 0) {
          console.log("输入任务名称不能为空");
          return;
        }
        // 准备数据
        const todo = {
          id: +Date.now(),
          name: this.newTodo.trim(),
        };
        // 添加到数组尾部
        this.todos.push(todo);
        // 清空输入框
        this.newTodo = "";
      },

      clearTodos() {
        // 清空数组
        this.todos = [];
      },
    },
  });
</script>
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <link rel="stylesheet" href="./index-min.css" />
  <title>记事本</title>
</head>

<body>
  <!-- 主体区域 -->
  <section id="app">
    <!-- 输入框 -->
    <header class="header">
      <h1>小黑记事本</h1>
      <input v-model="newTodo" placeholder="请输入任务" class="new-todo" />
      <button @click="addTodo" class="add">添加任务</button>
    </header>
    <!-- 列表区域 -->
    <section class="main">
      <ul class="todo-list">
        <!-- 列表项,使用 v-for 指令,遍历数组,渲染列表项 -->
        <li class="todo" v-for="(item, index) in todos" :key="item.id">
          <div class="view">
            <span class="index">{{index+1}}.</span>
            <label>{{item.name}}</label>
            <button class="destroy" @click="removeTodo(item.id)"></button>
          </div>
        </li>
      </ul>
    </section>
    <!-- 统计和清空 -->
    <footer class="footer" v-show="todos.length > 0">
      <!-- 统计 -->
      <span class="todo-count">合 计:<strong> {{todos.length}} </strong></span>
      <!-- 清空 -->
      <button @click="clearTodos" class="clear-completed">清空任务</button>
    </footer>
  </section>

  <!-- 底部 -->
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: "#app",
      data: {
        // 准备数据用于列表初始渲染
        newTodo: "",
        todos: [
          { id: 1, name: "学习 HTML" },
          { id: 2, name: "学习 CSS" },
          { id: 3, name: "学习 JavaScript" },
          { id: 4, name: "学习 Vue" },
        ],
      },
      methods: {
        removeTodo(id) {
          // this.todos = this.todos.filter(item => item.id !== id)
          // 根据 id 找到对应的索引
          const index = this.todos.findIndex((item) => item.id === id);
          // 根据索引,删除对应的元素
          // splice() 方法用于删除数组中的元素,第一个参数是索引,第二个参数是删除的个数
          this.todos.splice(index, 1);
        },

        addTodo() {
          // 判断输入框中是否有内容
          if (this.newTodo.trim().length === 0) {
            console.log("输入任务名称不能为空");
            return;
          }
          // 准备数据
          const todo = {
            id: +Date.now(),
            name: this.newTodo.trim(),
          };
          // 添加到数组尾部
          this.todos.push(todo);
          // 清空输入框
          this.newTodo = "";
        },

        clearTodos() {
          // 清空数组
          this.todos = [];
        },
      },
    });
  </script>
</body>

</html>

指令修饰符

什么是指令修饰符?

  • 所谓指令修饰符就是通过 . 指明一些指令后缀
  • 不同的后缀封装了不同的处理操作 —> 简化代码

按键修饰符

  • @keyup.enter —> 当点击 Enter(回车键) 键的时候才触发
html
<!-- 主体区域 -->
<section id="app">
  <!-- 输入框 -->
  <header class="header">
    <h1>小黑记事本</h1>
    <input @keydown.enter="addTodo" v-model="newTodo" placeholder="请输入任务" class="new-todo" />
    <button @click="addTodo" class="add">添加任务</button>
  </header>
  <!-- 列表区域 -->
  <section class="main">
    <ul class="todo-list">
      <!-- 列表项,使用 v-for 指令,遍历数组,渲染列表项 -->
      <li class="todo" v-for="(item, index) in todos" :key="item.id">
        <div class="view">
          <span class="index">{{index+1}}.</span>
          <label>{{item.name}}</label>
          <button class="destroy" @click="removeTodo(item.id)"></button>
        </div>
      </li>
    </ul>
  </section>
  <!-- 统计和清空 -->
  <footer class="footer" v-show="todos.length > 0">
    <!-- 统计 -->
    <span class="todo-count">合 计:<strong> {{todos.length}} </strong></span>
    <!-- 清空 -->
    <button @click="clearTodos" class="clear-completed">清空任务</button>
  </footer>
</section>

<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      // 准备数据用于列表初始渲染
      newTodo: "",
      todos: [
        { id: 1, name: "学习 HTML" },
        { id: 2, name: "学习 CSS" },
        { id: 3, name: "学习 JavaScript" },
        { id: 4, name: "学习 Vue" },
      ],
    },
    methods: {
      removeTodo(id) {
        // this.todos = this.todos.filter(item => item.id !== id)
        // 根据 id 找到对应的索引
        const index = this.todos.findIndex((item) => item.id === id);
        // 根据索引,删除对应的元素
        // splice() 方法用于删除数组中的元素,第一个参数是索引,第二个参数是删除的个数
        this.todos.splice(index, 1);
      },

      addTodo() {
        // 判断输入框中是否有内容
        if (this.newTodo.trim().length === 0) {
          console.log("输入任务名称不能为空");
          return;
        }
        // 准备数据
        const todo = {
          id: +Date.now(),
          name: this.newTodo.trim(),
        };
        // 添加到数组尾部
        this.todos.push(todo);
        // 清空输入框
        this.newTodo = "";
      },

      clearTodos() {
        // 清空数组
        this.todos = [];
      },
    },
  });
</script>
html
<div id="app">
  <h3>@keyup.enter → 监听键盘回车事件</h3>
  <input @keyup.enter="print" v-model="username" type="text" placeholder="请输入用户名" />
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      username: "",
    },
    methods: {
      print(e) {
        // if (e.key === 'Enter') {
        //   console.log('键盘回车的时候触发', this.username)
        // }

        console.log("键盘回车的时候触发", this.username);
      },
    },
  });
</script>

v-model 修饰符

  • v-model.trim —> 去除首位空格
  • v-model.number —> 转数字
html
<div id="app">
  <h3>v-model 修饰符 .trim .number</h3>
  <ul>
    <li><code>.trim</code> 用于移除字符串头尾的空格</li>
    <li><code>.number</code> 用于将字符串转换为数字</li>
  </ul>
  姓名:<input v-model.trim="username" type="text" placeholder="请输入姓名"><br>
  年龄:<input v-model.number="age" type="text" placeholder="请输入年龄"><br>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      username: '',
      age: '',
    },
  });
</script>

事件修饰符

  • @事件名.stop —> 阻止冒泡
  • @事件名.prevent —> 阻止默认行为
  • @事件名.stop.prevent —> 可以连用 即阻止事件冒泡也阻止默认行为
html
<h3>@事件名.stop → 阻止冒泡</h3>
<fieldset>
  <legend>有 事件修饰符 stop</legend>
  <div id="app1">
    <div @click="fatherFn" class="father">
      <div @click.stop="sonFn" class="son">儿子</div>
    </div>
  </div>
</fieldset>
<fieldset>
  <legend>无 事件修饰符 stop</legend>
  <div id="app2">
    <div @click="fatherFn" class="father">
      <div @click="sonFn" class="son">儿子</div>
    </div>
  </div>
</fieldset>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app1 = new Vue({
    el: "#app1",
    methods: {
      fatherFn() {
        alert("老父亲被点击了");
      },
      sonFn(e) {
        // e.stopPropagation()
        alert("儿子被点击了");
      },
    },
  });
  const app2 = new Vue({
    el: "#app2",
    methods: {
      fatherFn() {
        alert("老父亲被点击了");
      },
      sonFn(e) {
        // e.stopPropagation()
        alert("儿子被点击了");
      },
    },
  });
</script>
html
<div id="app">
  <h3>@事件名.prevent → 阻止默认行为</h3>
  <a @click.prevent href="http://www.baidu.com">阻止默认行为</a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
  });
</script>

v-bind 对于样式操作的增强

为了方便开发者进行样式控制,Vue 扩展了 v-bind 的语法,可以针对 class 类名style 行内样式 进行控制。

操作 class

官方文档:Class 与 Style 绑定 — Vue.js

语法

jsx
<div :class="对象/数组">这是一个 div</div>

对象语法

  • 当 class 动态绑定的是对象时,键就是类名,值就是布尔值
  • 如果值是true,就有这个类,否则没有这个类
  • 适用场景:一个类名,来回切换
jsx
<div class="box" :class="{ 类名1: 布尔值, 类名2: 布尔值 }"></div>

数组语法

  • 当 class 动态绑定的是数组时 → 数组中所有的类,都会添加到盒子上
  • 本质就是一个 class 列表
  • 使用场景:批量添加或删除类
jsx
<div class="box" :class="[ 类名1, 类名2, 类名3 ]"></div>
html
<div id="app">
  <fieldset>
    <legend>原始</legend>
    <div class="box">黑马程序员</div>
  </fieldset>
  <fieldset>
    <legend>:class="{ pink: true, big: true }"</legend>
    <div class="box" :class="{ pink: true, big: true }">黑马程序员</div>
  </fieldset>
  <fieldset>
    <legend>:class="{ pink: true, big: false }"</legend>
    <div class="box" :class="{ pink: true, big: false }">黑马程序员</div>
  </fieldset>
  <fieldset>
    <legend>:class="['pink', 'big']"</legend>
    <div class="box" :class="['pink', 'big']">黑马程序员</div>
  </fieldset>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {},
  });
</script>

案例 京东秒杀 tab 高亮导航

核心思路:所谓切换高亮,其实就是改下标

  1. 基于数据动态染 tab -> v-for
  2. 准备下标记录高亮的是哪一人 tab → activeIndex
  3. 基于下标,动态控制 class 类名 → v-bind:class="{active: activeIndex === index}"
html
<div id="app">
  <ul>
    <li><a class="active" href="#">京东秒杀</a></li>
    <li><a href="#">每日特价</a></li>
    <li><a href="#">品类秒杀</a></li>
  </ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      list: [
        { id: 1, name: "京东秒杀" },
        { id: 2, name: "每日特价" },
        { id: 3, name: "品类秒杀" },
      ],
    },
  });
</script>
html
<div id="app">
  <ul>
    <!-- list: 使用 v-for 遍历数组数据,生成列表元素 -->
    <!-- 鼠标点击后动态修改 activeIndex 的值,实现高亮效果 -->
    <li v-for="(item, index) in tabs" :key="item.id" @click="activeIndex = index">
      <a :class="{ active: activeIndex === index }" :href="'javascript:;'">
        {{ item.name }}
      </a>
    </li>
  </ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      activeIndex: 2,
      tabs: [
        { id: 1, name: "京东秒杀" },
        { id: 2, name: "每日特价" },
        { id: 3, name: "品类秒杀" },
      ],
    },
  });
</script>

操作 style

官方文档:Class 与 Style 绑定 — Vue.js

语法

html
<div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div>
html
<div id="app">
  <!-- v-bind 对于样式操作的增强,可以使用 v-bind 对应的属性,实现动态设置样式 -->

  <!-- CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名 -->
  <!-- 驼峰式:background-color: 'green' → backgroundColor: 'green' -->
  <!-- 短横线分隔:font-size: 22px → "font-size": "22px" -->
  <div class="box" :style="{ width: '400px', height: '400px', backgroundColor: 'green' }"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {},
  });
</script>

案例 进度条效果

核心思路:所谓进度条,其实就是改宽度

  1. :style="{width: percent + '%'}"v-bind 绑定样式
  2. percent: 0data 中准备数据
html
<div id="app">
  <div class="progress">
    <div class="inner">
      <span>50%</span>
    </div>
  </div>
  <button>设置 25%</button>
  <button>设置 50%</button>
  <button>设置 75%</button>
  <button>设置 100%</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {},
  });
</script>
html
<div id="app">
  <div class="progress">
    <div class="inner" v-bind:style="{ width: progress + '%' }">
      <span>{{progress}}%</span>
    </div>
  </div>
  <button @click="progress = 0">设置 0%</button>
  <button @click="progress = 25">设置 25%</button>
  <button @click="progress = 50">设置 50%</button>
  <button @click="progress = 75">设置 75%</button>
  <button @click="progress = 100">设置 100%</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      progress: 50,
    },
  });
</script>

v-model 应用于其他表单元素

  • 常见的表单元素都可以用 v-model 绑定关联 → 快速 获取设置 表单元素的值
  • 它会根据 控件类型 自动选取 正确的方法 来更新元素
  • 输入框 input:text ——> value
  • 文本域 textarea ——> value
  • 复选框 input:checkbox ——> checked
  • 单选框 input:radio ——> checked
  • 下拉菜单 select ——> value
html
<div id="app">
  <h3>小黑学习网</h3>

  <label>姓名:
    <input type="text" v-model="username" placeholder="请输入姓名" />
  </label>
  <br /><br />

  <label>是否单身:
    <input type="checkbox" v-model="isSingle" />
  </label>
  <br /><br />

  <!--
    前置理解:
      1. name:  给单选框加上 name 属性 可以分组 → 同一组互相会互斥
      2. value: 给单选框加上 value 属性,用于提交给后台的数据
    结合 Vue 使用 → v-model
  -->
  <label>性别: <input type="radio" name="gender" v-model="gender" value="1" />男
    <input type="radio" name="gender" v-model="gender" value="2" />女
  </label>
  <br /><br />

  <!--
    前置理解:
      1. option 需要设置 value 值,提交给后台
      2. select 的 value 值,关联了选中的 option 的 value 值
    结合 Vue 使用 → v-model
  -->
  <label>所在城市:
    <select v-model="cityId">
      <option value="10001">北京</option>
      <option value="10002">上海</option>
      <option value="10003">成都</option>
      <option value="10004">南京</option>
    </select>
  </label>
  <br /><br />

  <label>自我描述:
    <textarea v-model="desc"></textarea>
  </label>

  <button>立即注册</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      username: '',
      isSingle: false,
      gender: '2',
      cityId: '10001',
      desc: '',
    },
  });

  setTimeout(() => {
    app.username = '黑马程序员';
    app.isSingle = true;
    app.gender = '1';
    app.cityId = '10004';
    app.desc = '我是一个爱学习的好青年';
  }, 3000);
</script>